package edu.gatech.ors.mrsim.core;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.PriorityQueue;

import org.apache.log4j.Logger;

import edu.gatech.ors.mrsim.helpers.ReflectionUtil;
import edu.gatech.ors.mrsim.io.conf.ConfigurableClass;
import edu.gatech.ors.mrsim.io.conf.Configuration;
import edu.gatech.ors.mrsim.io.logging.Logging;

/**
 * Represents the entire system, and contains the simulate() method.
 * 
 * @author tdoneal, Andrey K.
 * 
 */
public class Simulation extends ConfigurableClass {

	private static Logger LOGGER = Logging.getInstance().getClassLogger(Simulation.class);

	// the priority queue of events
	protected PriorityQueue<Event<?>> events = new PriorityQueue<Event<?>>();
	protected HashMap<String, Module> modules;
	protected Cluster cluster;
	protected SimJob job;
	private String name;

	protected final static String MODULES_KEY = "Modules";
	protected final static String SIMJOB_KEY = "SimJob";
	protected final static String CLUSTER_KEY = "ClusterCreator";

	private boolean started, over;

	public Simulation(String name, Configuration config) {
		super(config);
		this.name = name;
		modules = new HashMap<String, Module>();
		events = new PriorityQueue<Event<?>>();
	}

	@Override
	public boolean loadValuesFromConfiguration() {
		if (hasConfigurationRedirect())
			configuration = getConfigurationRedirect(LOGGER);
		if (configuration == null)
			return false;
		boolean toReturn = true;

		try {
			Class<?> clusterCreatorClass = configuration.getClass(CLUSTER_KEY);
			ClusterCreator creator = (ClusterCreator) clusterCreatorClass.newInstance();
			cluster = creator.createCluster(configuration);
			if (!creator.clusterSuccess())
				toReturn = false;
		} catch (ClassNotFoundException | NullPointerException e) {
			toReturn = false;
			LOGGER.error("Cluster not loaded successfuly - StackTrace follows:\n", e);
		} catch (InstantiationException | IllegalAccessException e) {
			toReturn = false;
			LOGGER.error("ClusterCreator not instantiated successfully - StackTrace follows:\n", e);
		}

		try {
			Class<?>[] moduleClasses = configuration.getClasses(MODULES_KEY);
			for (Class<?> moduleClass : moduleClasses) {
				try {
					Module loadedModule = ReflectionUtil.attemptConstructorCallAndCast(Module.class, moduleClass, this,
							configuration);
					if (loadedModule == null) {
						LOGGER.error("Could not instantiate provided module " + moduleClass.getName()
								+ ". Check that it has a constructor that takes a Simulation and a Configuration.");
						toReturn = false;
					} else {
						boolean loaded = loadedModule.loadValuesFromConfiguration();
						if (toReturn)
							toReturn = loaded;
						addModule(loadedModule);
					}
				} catch (Exception e) {// gotta catch them all - handle properly
					toReturn = false;
					LOGGER.error("Modules not loaded successfuly - StackTrace follows:\n", e);
				}

			}
		} catch (ClassNotFoundException e) {
			toReturn = false;
			LOGGER.error(
					"Modules classes not loaded successfuly - check names in provided configuration. StackTrace follows:\n",
					e);
		}

		try {
			Class<?> simjobClass = configuration.getClass(SIMJOB_KEY);
			job = (SimJob) simjobClass.newInstance();
			if (job == null) {
				toReturn = false;
				LOGGER.error("SimJob " + simjobClass + " not loaded correctly");
			}
		} catch (ClassNotFoundException e) {
			toReturn = false;
			LOGGER.error("SimJob not loaded successfuly - StackTrace follows:\n", e);
		} catch (InstantiationException | IllegalAccessException e) {
			toReturn = false;
			LOGGER.error("SimJob not instantiated successfully - StackTrace follows:\n", e);
		}

		if (!toReturn)
			LOGGER.error("Simulation " + getName() + " not loaded correctly - may not run or function. Check logs.");
		return toReturn;
	}

	protected void start() {
		started = true;
		over = false;
		List<Event<?>> initEvents = job.getInitEvents(configuration, this);
		events.addAll(initEvents);
		int count = 0;
		while (!events.isEmpty()) {
			Event<? extends Node> nextEvent = events.poll();
			boolean handled = false;
			for (Module module : modules.values()) {
				if (module.handleEvent(nextEvent)) {
					handled = true;
				}
			}
			if (!handled) {
				LOGGER.error("Event " + nextEvent + " could not be handled by modules.");
			} else {
				LOGGER.info("Event #" + count + " handled. Description: " + nextEvent.toString());
				count++;
			}
		}
		started = false;
		over = true;
	}

	public boolean isStarted() {
		return started;
	}

	public boolean isOver() {
		return over;
	}

	public boolean canRun() {
		return this.isSuccessfulLoad();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see edu.gatech.ors.mrsim.core.ConfigurableClass#getName()
	 */
	public String getName() {
		return name;
	}

	/**
	 * @return the cluster
	 */
	public Cluster getCluster() {
		return cluster;
	}

	/**
	 * @param cluster
	 *            the cluster to set
	 */
	public void setCluster(Cluster cluster) {
		this.cluster = cluster;
	}

	/**
	 * @param moduleName
	 * @return
	 */
	public Module getModule(String moduleName) {
		return modules.get(moduleName);
	}

	/**
	 * @param toAdd
	 */
	protected void addModule(Module toAdd) {
		modules.put(toAdd.getName(), toAdd);
	}

	public void enqueueEvent(Event<?> event) {
		if (event != null)
			this.events.add(event);
	}

	public void enqueueEvents(Collection<Event<?>> events) {
		if (events != null)
			this.events.addAll(events);
	}

}
